package demoMod.stsconsole.cards;

import demoMod.stsconsole.ConsoleEntry;
import demoMod.stsconsole.annotations.DataSaver;
import demoMod.stsconsole.characters.AbstractPlayer;
import demoMod.stsconsole.core.AbstractCreature;
import demoMod.stsconsole.dungeons.AbstractDungeon;
import demoMod.stsconsole.enums.SubscriberPriority;
import demoMod.stsconsole.helpers.DamageInfo;
import demoMod.stsconsole.helpers.LocalizedStrings;
import demoMod.stsconsole.helpers.RandomSequence;
import demoMod.stsconsole.interfaces.*;
import demoMod.stsconsole.saves.CardSaver;

import java.lang.reflect.InvocationTargetException;
import java.util.*;

@DataSaver(cls = CardSaver.class)
public abstract class AbstractCard implements ReloadStringsSubscriber, CustomSavable<Map<String, Object>>, Saleable, ChangePriceSubscriber {
    public String id;
    public String name;
    public String description;
    public int cost;

    public CardColor color;
    public CardType type;
    public CardRarity rarity;

    public int baseDamage;
    public int damage;

    public int baseBlock;
    public int block;

    public int magicNumber;
    public int misc;

    public int upgradeTimes = 0;
    public boolean upgraded = false;

    public boolean canUse = true;
    public boolean exhaust = false;
    public boolean isEthereal = false;
    public boolean isInnate = false;
    public boolean retain = false;
    public boolean returnToHand = false;
    public boolean freeToPlayOnce = false;
    public boolean purge = false;
    public boolean isPriceInitialized = false;
    public boolean canRemoveFromMasterDeck = true;

    public List<CardTags> tags = new ArrayList<>();
    public CardTarget target;

    public UUID uuid;
    private int price;

    public AbstractCard(String id) {
        this.id = id;
        this.uuid = UUID.randomUUID();
        onReloadStrings();
        ConsoleEntry.addLater.add(this);
    }

    @Override
    public void onReloadStrings() {
        List<String> cardStrings = LocalizedStrings.getCardStrings(this.id);
        this.name = cardStrings.get(0);
        this.description = cardStrings.get(1);
    }

    @Override
    public boolean enabled(Class<? extends ISubscriber> cls) {
        AbstractPlayer p = AbstractDungeon.player;
        return cls.getSimpleName().equals("ReloadStringsSubscriber") || cls.getSimpleName().equals("ChangePriceSubscriber") || (p != null && (
                p.drawPile.contains(this) ||
                p.hand.contains(this) ||
                p.discardPile.contains(this) ||
                p.exhaustPile.contains(this)
            )
        );
    }

    @Override
    public int getPriority() {
        return SubscriberPriority.CARD.getPriority();
    }

    public void onRemove() {
        ConsoleEntry.removeLater.add(this);
    }

    public String getUpdatedDescriptions(AbstractCreature target) {
        this.calculateCardDamage(target);
        String ret = this.description.replace("!D!", Integer.toString(this.damage));
        ret = ret.replace("!B!", Integer.toString(this.block));
        return ret.replace("!M!", Integer.toString(this.magicNumber));
    }

    public abstract void upgrade();

    public abstract boolean use(AbstractPlayer p, AbstractCreature target);

    public AbstractCard makeCopy() {
        try {
            return this.getClass().newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    public AbstractCard makeStatEquivalentCopy() {
        AbstractCard card = makeCopy();
        card.misc = this.misc;
        for (int i=0;i<this.upgradeTimes;i++) {
            card.upgrade();
        }
        card.uuid = this.uuid;
        return card;
    }

    public void calculateCardDamage(AbstractCreature target) {
        DamageInfo info = new DamageInfo(this.baseDamage, AbstractDungeon.player);
        info.applyPowers(target);
        this.damage = (int) info.output;

        this.block = this.baseBlock;
        ConsoleEntry.triggerSubscribers(
            BlockSubscriber.class,
            "onDefendToChangeBlock",
            new Class[] { int.class, AbstractCreature.class },
            new Object[] { this.block, AbstractDungeon.player },
            (method, params, subscriber) -> {
                try {
                    params[0] = method.invoke(subscriber, params);
                    this.block = (int) params[0];
                } catch (IllegalAccessException | InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        );
    }

    @Override
    public Map<String, Object> onSave() {
        Map<String, Object> ret = new HashMap<>();
        ret.put("upgradeTimes", this.upgradeTimes);
        ret.put("misc", this.misc);
        ret.put("isInnate", this.isInnate);
        return ret;
    }

    @Override
    public void onLoad(Map<String, Object> save) {
        if (save != null) {
            int t = (int) save.get("upgradeTimes");
            this.misc = (int) save.get("misc");
            if (save.get("isInnate") != null) {
                this.isInnate = (boolean) save.get("isInnate");
            }
            for (int i=0;i<t;i++) {
                this.upgrade();
            }
        }
    }

    @Override
    public int getBasePrice() {
        int price = 9999;
        switch (this.rarity) {
            case BASIC:
            case COMMON:
                price = 50;
                break;
            case UNCOMMON:
                price = 75;
                break;
            case RARE:
                price = 140;
                break;
            case CURSE:
                price = 0;
                break;
            case SPECIAL:
                price = 400;
        }
        return price;
    }

    @Override
    public int getPrice() {
        return this.price;
    }

    @Override
    public void calculatePrice(RandomSequence rng) {
        this.price = getBasePrice();
        ConsoleEntry.triggerSubscribers(
                ChangePriceSubscriber.class,
                "onChangePrice",
                new Class[]{Saleable.class, int.class, RandomSequence.class},
                new Object[]{this, this.price, rng},
                (method, params, subscriber) -> {
                    if (params[0] == this) {
                        try {
                            params[1] = method.invoke(subscriber, params);
                        } catch (IllegalAccessException | InvocationTargetException e) {
                            e.printStackTrace();
                        }
                    }
                }
        );
    }

    @Override
    public void purchase() {
        AbstractDungeon.player.addCardToMasterDeck(this);
    }

    @Override
    public int onChangePrice(Saleable product, int price, RandomSequence rng) {
        if (product == this) {
            if (!isPriceInitialized) {
                price *= (rng.nextFloat() - 0.5F) * 0.2F + 1.0F;
                this.isPriceInitialized = true;
            }
            this.price = price;
        }
        return price;
    }

    public enum CardColor {
        RED, GREEN, BLUE, PURPLE, COLORLESS, CURSE
    }

    public enum CardRarity {
        BASIC, COMMON, UNCOMMON, RARE, SPECIAL, CURSE
    }

    public enum CardType {
        ATTACK, SKILL, POWER, STATUS, CURSE
    }

    public enum CardTags {
        STRIKE, STARTER_STRIKE, STARTER_DEFEND, HEAL
    }

    public enum CardTarget {
        SELF, ENEMY, ALL_ENEMY
    }
}
